python

您所在的位置:网站首页 opencv 框选 python

python

2023-03-14 22:47| 来源: 网络整理| 查看: 265

  整体思路:

  1.原图灰度化  

  2.灰度图截取mask区域  

  3.mask区域二值化  

  4.二值化图像运算(开运算)  

  5.原灰图轮廓提取 

  6.不规则轮廓校准(外接矩形/内接矩形)

注:代码依次头尾连接哦!

0.第三方库导入

import cv2 as cv import numpy as np import imutils import matplotlib.pyplot as plt import MightexUSBcameraSDK as Cam import math

 

1.原图灰度化

1 img = cv.imread(r"D:\picture\p7.png", cv.COLOR_GRAY2BGR) 2 gray = cv.imread(r"D:\picture\p7.png", cv.COLOR_BGR2GRAY)

 

2.灰度图截取mask区域

 

 

 

 代码中省去了图中右侧的两个像素值分布图。从原灰度图选取一个mask区间,区间是基于图左两张图的XY像素坐标,截取自己感兴趣的mask区域,省去毫不关心并可能影响图像处理的区域。

mask = np.zeros(gray.shape[:2], np.uint8)mask[0:430, 240:530] = 255 # 裁剪出mask区域mask_hist = cv.calcHist([gray], [0], mask, [256], [0, 256]) # 计算mask的直方图 灰度图通道=[0]

# 8 通过位运算,计算有mask的灰度图片mask_img = cv.bitwise_and(gray, gray, mask=mask)

# show_image(mask_img, "gray image with mask", 3, "X pixel pos", "Y pixel pos")# show_histogram(mask_hist, "histogram with masked gray image", 4,# "m", "Gray value (black~white:0~255)", "Number of pixels")# plt.show() # 显示画布

img = mask_img # img 作为原灰图,mask_img 为mask区域的灰度图

 

3.mask区域二值化

 

 

 

 图像二值化,可理解为将图形与背景,通过一个阈值X,低于阈值X的视为黑色0,高于X的视为灰色1。

# (1).灰度化图像 gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # (2).高斯滤波 blurred = cv.GaussianBlur(gray, (5, 5), 0) # thresh = cv.threshold(blurred, 64, 80, cv.THRESH_BINARY)[1] # (3).二值化图像(化为0,1) thresh = cv.threshold(blurred, 61, 80, cv.THRESH_BINARY)[1] # 调整高/低阈值,以匹配最好的识别效果 cv.imshow("thresh", thresh) # thresh——二值化,具体将闭合轮廓整体填充为灰色,其余(背景)为黑色

4.二值化图像运算(开运算)

 

 

  本人所关心的轮廓是基于类似椭圆状的区域部分,所以应想办法将尖端 "腐蚀" 掉。

  先腐蚀后膨胀——即为开运算;相对的反过来就是——闭运算。这两类运算目的,是为了消除原图拍摄时产生的轮廓(内/外)毛刺、尖端、或者噪音。此处我用开运算主要是为了腐蚀掉我不关心的轮廓尖端。

   很明显通过腐蚀后再膨胀,轮廓变得更加圆滑,并消除了尖端。

 

 

 

# (4).开运算[先膨胀-后腐蚀],尝试去除噪声(去除尖端) img2 = thresh.copy() k = np.ones((10, 10), np.uint8) # 卷积核 如(10, 10)= 10X10的矩阵(或称数组) thresh_open = cv.morphologyEx(img2, cv.MORPH_OPEN, k) # 开运算[先膨胀-后腐蚀] cv.imshow("open operation", thresh_open) # 暂时屏蔽

 5.原灰图轮廓提取

 

 

 

   cnts 返回所有轮廓集合,并计算轮廓质心(cX, cY),注意这里指质心,而不是规则图形的中心,质心会随着图中部分轮廓的凹陷而稍微偏移。c 很好理解,则是cnts遍历出来的每一个轮廓。将 c 取出是为了对每个轮廓进行内接矩形的捕捉等等,从代码中可以看出 c 被当成一个参数传入进行运算。

   可以print (M)进行打印查看,就能理解cX,cY是通过轮廓总面积进行计算求取。

  x_min, x_max, y_min, y_max,则是求取轮廓的四点极值,也就是后面计算内接矩的一个标准范围。

# (5).下一步是使用轮廓检测​​找到这些白色区域的位置:返回轮廓个数 cnts = cv.findContours(thresh_open.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) # RETR_EXTERNAL cnts = imutils.grab_contours(cnts) # 返回轮廓 contours —— cnts # (6).cnts 返回的是所有轮廓,所以需要for循环来遍历每一个轮廓 for i, c in enumerate(cnts): # 计算轮廓区域的图像矩。 在计算机视觉和图像处理中,图像矩通常用于表征图像中对象的形状。 # 这些力矩捕获了形状的基本统计特性,包括对象的面积,质心(即,对象的中心(x,y)坐标), # 方向以及其他所需的特性。 M = cv.moments(c) # m00是图像面积(白色区域)的总和,或者说连通域的面积;而这时m10和m01是图像白色区域上x和y坐标值的累计 cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) # 1. 绘制最大内接圆 r = drawInCircle(thresh_open, img, c, cX, cY) # 2. 计算最小外接正矩形的四个顶点,是否绘制外矩形框 x_min, x_max, y_min, y_max = drawOutRectgle(c, False) # 3. 最大内接矩形 x1, x2, y1, y2 = drawInRectgle(img, c, cX, cY, x_min, x_max, y_min, y_max) cv.drawContours(img, [c], -1, (0, 255, 0), 1) # 最外层轮廓绘制 cv.circle(img, (cX, cY), 1, (255, 255, 255), -1) # 轮廓中心点 cv.putText(img, "center%d=%s avg=%d" % (i, bgr_val, gray_avg), (cX - 90, cY - 16), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)

6.不规则轮廓校准(外接矩形/内接矩形)

(1)最大内接圆

遍历每个轮廓的所有坐标,寻求基于中心坐标(轮廓质心)最大圆直径

 

 

  白字补充的分别是质心的灰度值,和白框矩形内所有亮度的平均灰度值,这里可以忽略不是重点。 

 

def drawInCircle(img_open, img, cont, cX, cY): # 绘制最大内接圆 # 最大内接圆——检索轮廓的方式 c = cont # 单个轮廓 contours = cv.findContours(img_open.copy(), cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) src = img_open.copy() raw_dist = np.empty(src.shape, dtype=np.float32) for ii in range(src.shape[0]): for jj in range(src.shape[1]): raw_dist[ii, jj] = cv.pointPolygonTest(c, (jj, ii), True) # 检测点坐标,与c轮廓坐标的距离 minVal, maxVal, _, maxDistPt = cv.minMaxLoc(raw_dist) # minMaxLoc查找最小和最大元素值及其位置ma maxVal = abs(maxVal) cv.circle(img, (cX, cY), np.int(maxVal)-1, (255, 255, 255), 1, cv.LINE_8, 0) # 最大内接圆 height = np.int(maxVal)-1 return np.int(maxVal)-1

(2)最大内接正矩形(中心延展算法——个人理解)

  中心延展法,其实就是中心坐标从 (cX, cY) 开始向四周延展。理解很简单,即从单个像素(cX, cY) 分别从第二、三象限,到第一、四象限(数学上以中心坐标)进行+1延展,但是此处图像很明显X:Y的比例大致将近 5:1或者4:1,故而算法实现中不能Y延展1,再到X延展1来类推。通过比例进行Y延展1,再到X延展4,再判断是否超出轮廓,若超出轮廓X部分需要遍历返回步进1(即),直到到达轮廓。例如X向左延展需要同时判定,第二、三象限皆满足条件(不超轮廓),向右延展则满足一、四象限类推,Y也是如此。

  “# 取轴更长范围作for循环”,这前部分代码是自动计算了轮廓的X:Y的比例关系,radio 为长轴比短轴的比例,也直接在延展中将X、Y的步进值与radio联系上,比较“人性化”的计算,所以当遇到Y比X长的轮廓,这个算法也适用。这部分可以写死简化,看个人取舍。

  重点是理解核心算法,位于注释 "# 第二象限延展" 即为算法的开端,当完成第二象限的延展时,返回一个标志位提示完成;当四个象限延展判断完成时,则提前终止for循环。

  有人会好奇,为何我用的不是双for循环对X、Y的遍历,其实只要理解中心延展法,其实如何写就是自己的喜好;起初我也是用双for,但是发现没必要而且更复杂,就想到了比例延展的思路,没必要每次只延展步进1,这样效率也比较低;按比例延展,只要超出轮廓才进行遍历返回(每次返回1直到回到轮廓上)。

def drawInRectgle(img, cont, cX, cY, x_min, x_max, y_min, y_max): """绘制不规则最大内接正矩形""" # img 对应的是原图, 四个极值坐标对应的是最大外接矩形的四个顶点 c = cont # 单个轮廓 # print(c) range_x, range_y = x_max - x_min, y_max - y_min # 轮廓的X,Y的范围 x1, x2, y1, y2 = cX, cX, cY, cY # 中心扩散矩形的四个顶点x,y cnt_range, radio = 0, 0 shape_flag = 1 # 1:轮廓X轴方向比Y长;0:轮廓Y轴方向比X长 if range_x > range_y: # 判断轮廓 X方向更长 radio, shape_flag = int(range_x / range_y), 1 range_x_left = cX - x_min range_x_right = x_max - cX if range_x_left >= range_x_right: # 取轴更长范围作for循环 cnt_range = int(range_x_left) if range_x_left = range_y_bottom: # 取轴更长范围作for循环 cnt_range = int(range_y_top) if range_y_top


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3